#ifndef _GWBASE_BASE_BUFFER_H_
#define _GWBASE_BASE_BUFFER_H_

#include <arpa/inet.h>
#include <stddef.h>
#include <vector>
#include <boost/noncopyable.hpp>
#include "gwbase/base/slice.h"
#include "gwbase/base/coding.h"
#include "gwbase/base/logging.h"

namespace dicom {

// Data is stored in a single continuous memory block, when it
// is no space for new data, we resize the memory block by twice.
// There is only fixed space for prepending data, so no more than
// kPrependSpace bytes can be prepended.
// |-----------[-------------]------------|
//head        begin         end           tail
class Buffer: public boost::noncopyable {
  static const size_t kInitSize = 1024;
  static const size_t kPrependSpace = 64;
 public:
  Buffer()
      : buf_(kInitSize),
      read_index_(kPrependSpace),
      write_index_(kPrependSpace) { }

  ~Buffer();

  void Append(const void* buf, size_t len) {
    MakeRoom(len);
    ::memcpy(end(), buf, len);
    write_index_ += len;
  }

  void Append(const char* s) { Append(s, strlen(s)); }
  void Append(const std::string& s) { Append(s.c_str(), s.size()); }
  void Append(const Buffer& s) { Append(s.begin(), s.size()); }

  void Prepend(const void* buf, size_t len) {
    assert (prependable_bytes() >= len);
    memcpy(begin()-len, buf, len);
    read_index_ -= len;
  }

  void Retrieve(void* buf, size_t len) {
    assert (size() >= len);
    memcpy(buf, begin(), len);
    read_index_ += len;
  }

  uint32_t PeekUInt32(size_t pos = 0) {
    assert(size() >= sizeof(uint32_t));
    uint32_t h32 = GetUInt32(begin() + pos);
    return h32;
  }

  uint32_t ReadUInt32() {
    assert(size() >= sizeof(uint32_t));
    uint32_t h32 = GetUInt32(begin());
    Skip(sizeof(uint32_t));
    return h32;
  }

  uint64_t ReadUInt64() {
    assert(size() >= sizeof(uint64_t));
    uint64_t h64 = GetUInt64(begin());
    Skip(sizeof(uint64_t));
    return h64;
  }
  
  std::string ReadLengthStr() {
    assert (size() > sizeof(uint32_t));
    size_t len = ReadUInt32();

    assert (size() >= len);
    std::string str(begin(), len);
    Skip(len);
    return str;
  }

  void AppendUInt32(uint32_t v) {
    MakeRoom(sizeof(uint32_t));
    PutUInt32(end(), v);
    write_index_ += sizeof(uint32_t);
  }

  void AppendUInt64(uint64_t v) {
    MakeRoom(sizeof(uint64_t));
    PutUInt64(end(), v);
    write_index_ += sizeof(uint64_t);
  }

  void AppendLengthStr(const std::string& str) {
    AppendUInt32(static_cast<uint32_t>(str.size()));
    Append(str.c_str(), str.size());
  }

  void PrependUInt32(uint32_t v) {
    uint32_t be32 = hton32(v);
    Prepend(&be32, sizeof(be32));
  }

  void AppendChar(char c) { Append(&c, sizeof(c)); }

  ssize_t ReadFd(int fd);

  void Skip(size_t len) {
    assert (size() >= len);
    read_index_ += len;
    if (size() == 0) {
      read_index_ = kPrependSpace;
      write_index_ = kPrependSpace;
    }
  }

  void clear() {
    read_index_ = kPrependSpace;
    write_index_ = kPrependSpace;
  }

  char* head() { return &*buf_.begin(); }
  char* tail() { return &*buf_.end(); }

  char* begin() { return head() + read_index_; }
  const char* begin() const { return  &*buf_.begin() + read_index_; }
  char* end() { return head() + write_index_; }

  size_t size() const { return write_index_ - read_index_; }
  bool empty() const { return size() == 0; }

  size_t appendable_bytes() const { 
    return buf_.size() - write_index_; 
  }
  size_t prependable_bytes() const { return read_index_; }

  void MakeRoom(size_t len);

  // only used by BufferOutputStream
  void Written(size_t n) { 
    assert (appendable_bytes() >= n);
    write_index_ += n; 
  }
  void Unwritten(size_t n) { 
    assert (size() >= n);
    write_index_ -= n; 
  }

 private:
  std::vector<char> buf_;
  size_t read_index_;
  size_t write_index_;
};

}


#endif // _GWBASE_BASE_BUFFER_H_
